﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using UCS.XNA.Utils;
using UCS.XNA.Utils.Geometries;
using UCS.XNA.Utils.Physics;
using DivideAndConquer.States.InGame.Events;

namespace DivideAndConquer.States.InGame
{
    public class OpenField : IRectangle
    {
        #region Private Members

        private Texture2D _texture;
        private List<Ball> _balls = new List<Ball>();
        private Wall _currentActivaWall;
        private WallPreview _wallPreview;
        private Border _border;
        private MouseState _lastMouseState;

        #endregion

        #region Attributes and Properties

        private float _x;
        public float X
        {
            get { return _x; }
        }

        private float _y;
        public float Y
        {
            get { return _y; }
        }

        private float _width;
        public float Width
        {
            get { return _width; }
        }

        private float _height;
        public float Height
        {
            get { return _height; }
        }

        private float _scale;
        public float Scale
        {
            get { return _scale; }
            set { _scale = value; }
        }

        private WallOrientation _createWallOrientation;
        public WallOrientation CreateWallOrientation
        {
            get { return _createWallOrientation; }
            set { _createWallOrientation = value; }
        }

        private Vector2 _direction;
        public Vector2 Direction
        {
            get { return _direction; }
            set { _direction = value; }
        }

        private float _speedFactor;
        public float SpeedFactor
        {
            get { return _speedFactor; }
            set { _speedFactor = value; }
        }

        #endregion

        #region Events

        public event EventHandler<OpenFieldDividedEventArgs> Divided;

        #endregion

        #region Constructors

        public OpenField(Rectangle availableBounds, List<Ball> balls)
        {
            _x = availableBounds.X;
            _y = availableBounds.Y;
            _width = availableBounds.Width;
            _height = availableBounds.Height;
            _balls = balls;

            this.InitializeTexture();
            this.InitializeBorder();
        }

        #endregion

        #region Private Methods

        private void InitializeTexture()
        {
            _texture = Texture2DCreator.FromColor(Color.White, GameResources.Graphics.GraphicsDevice);
        }

        private void InitializeBorder()
        {
            _border = new Border(Texture2DCreator.FromColor(Color.DarkSlateGray, GameResources.Graphics.GraphicsDevice),
                Texture2DCreator.FromColor(Color.Black, GameResources.Graphics.GraphicsDevice),
                GameResources.SpriteBatch);

            _border.Bounds = new Rectangle((int)_x, (int)_y, (int)_width, (int)_height);
        }

        private void ManagePreviewCreation()
        {
            MouseState mouseState = Mouse.GetState();
            Vector2 mousePosition = new Vector2(mouseState.X, mouseState.Y);

            if (CollisionDetector.TestFullCollision(this, mousePosition))
            {
                if (_wallPreview == null || _wallPreview.Orientation != _createWallOrientation)
                {
                    Vector2 position;
                    float lenght;

                    if (_createWallOrientation == WallOrientation.Horizontal)
                    {
                        position = new Vector2(_x, mouseState.Y);
                        lenght = _width;
                    }
                    else
                    {
                        position = new Vector2(mouseState.X, _y);
                        lenght = _height;
                    }

                    _wallPreview = new WallPreview(position, lenght, _createWallOrientation);
                }
                else
                {
                    if (_createWallOrientation == WallOrientation.Horizontal)
                        _wallPreview.Y = mouseState.Y;
                    else
                        _wallPreview.X = mouseState.X;
                }
            }
            else
                _wallPreview = null;
        }

        private void ManageWallCreation()
        {
            if (_currentActivaWall != null)
                return;

            MouseState mouseState = Mouse.GetState();
            Vector2 mousePosition = new Vector2(mouseState.X, mouseState.Y);

                if (CollisionDetector.TestFullCollision(this, mousePosition) && 
                    _lastMouseState != null && _lastMouseState.LeftButton == ButtonState.Released && 
                    mouseState.LeftButton == ButtonState.Pressed)
                    _currentActivaWall = new Wall(mousePosition, _createWallOrientation);

            _lastMouseState = mouseState;
        }

        private void ManageCollisions()
        {
            this.ManageWallsCollisions();
            this.ManageBallsCollision();
        }

        private void ManageWallsCollisions()
        {
            if (_currentActivaWall == null)
                return;

            if (CollisionDetector.TestBoundsCollision(this, _currentActivaWall.SegmentA))
                _currentActivaWall.SegmentA.KeepGrowing = false;

            if (CollisionDetector.TestBoundsCollision(this, _currentActivaWall.SegmentB))
                _currentActivaWall.SegmentB.KeepGrowing = false;

            if (!_currentActivaWall.SegmentA.KeepGrowing && !_currentActivaWall.SegmentB.KeepGrowing)
            {
                this.Divide(_currentActivaWall);
                _currentActivaWall = null;
            }
        }

        private void ManageBallsCollision()
        {
            foreach (var ball in _balls)
            {
                this.ManageBallFieldCollision(ball);
                this.ManageBallWallsCollision(ball);
            }
        }

        private void ManageBallFieldCollision(Ball ball)
        {
            Vector2 resultCollision = CollisionDetector.TestBoundsCollision(this, ball);

            if (resultCollision != Vector2.Zero)
                ball.Direction *= resultCollision;
        }

        private void ManageBallWallsCollision(Ball ball)
        {
            if (_currentActivaWall == null)
                return;

            if (CollisionDetector.TestBoundsCollision(_currentActivaWall.SegmentA, ball) != Vector2.Zero ||
                CollisionDetector.TestBoundsCollision(_currentActivaWall.SegmentB, ball) != Vector2.Zero)
                _currentActivaWall = null;
        }

        private void Divide(Wall dividingWall)
        {
            Rectangle fieldARect;
            Rectangle fieldBRect;

            if (dividingWall.Orientation == WallOrientation.Horizontal)
            {
                float halfHeightA = dividingWall.SegmentA.Height / 2f;
                float halfHeightB = dividingWall.SegmentB.Height / 2f;

                fieldARect = new Rectangle(
                    (int)_x,
                    (int)_y,
                    (int)_width,
                    Convert.ToInt32(dividingWall.Position.Y - _y - halfHeightA));
                fieldBRect = new Rectangle(
                    (int)_x,
                    Convert.ToInt32(dividingWall.Position.Y + halfHeightB),
                    (int)_width,
                    Convert.ToInt32(_y + _height - dividingWall.Position.Y - halfHeightB));

                _height = dividingWall.Position.Y - _y;
            }
            else
            {
                float halfWidthA = dividingWall.SegmentA.Width / 2f;
                float halfWidthB = dividingWall.SegmentB.Width / 2f;

                fieldARect = new Rectangle(
                    (int)_x,
                    (int)_y,
                    Convert.ToInt32(dividingWall.Position.X - _x - halfWidthA),
                    (int)_height);
                fieldBRect = new Rectangle(
                    Convert.ToInt32(dividingWall.Position.X + halfWidthB),
                    (int)_y,
                    Convert.ToInt32(_x + _width - dividingWall.Position.X - halfWidthB),
                    (int)_height);

                _width = dividingWall.Position.X - _x;
            }

            List<Ball> fieldABalls = new List<Ball>();
            List<Ball> fieldBBalls = new List<Ball>();

            foreach (var ball in _balls)
            {
                if (CollisionDetector.TestFullCollision(fieldARect, new Vector2(ball.X, ball.Y)))
                    fieldABalls.Add(ball);
                else if (CollisionDetector.TestFullCollision(fieldBRect, new Vector2(ball.X, ball.Y)))
                    fieldBBalls.Add(ball);
            }

            OpenField fieldA = null;
            if (fieldABalls.Count > 0)
                fieldA = new OpenField(fieldARect, fieldABalls);

            OpenField fieldB = null;
            if (fieldBBalls.Count > 0)
                fieldB = new OpenField(fieldBRect, fieldBBalls);

            if (Divided != null)
                Divided(this, new OpenFieldDividedEventArgs(this, fieldA, fieldB));
        }

        #endregion

        #region Public Methods

        public void Update(GameTime gameTime)
        {
            this.ManagePreviewCreation();
            this.ManageWallCreation();
            this.ManageCollisions();

            foreach (var ball in _balls)
                ball.Update(gameTime);

            if (_currentActivaWall != null)
                _currentActivaWall.Update(gameTime);
        }

        public void Draw(GameTime gameTime)
        {
            GameResources.SpriteBatch.Begin();

            GameResources.SpriteBatch.Draw(texture: _texture,
                                           destinationRectangle: new Rectangle((int)_x, (int)_y, (int)_width, (int)_height),
                                           color: Color.White);

            GameResources.SpriteBatch.End();

            if (_wallPreview != null)
                _wallPreview.Draw(gameTime);

            if (_currentActivaWall != null)
                _currentActivaWall.Draw(gameTime);

            foreach (var ball in _balls)
                ball.Draw(gameTime);

            if (_border != null)
                _border.Draw(gameTime);
        }

        #endregion
    }
}